#include "http.h"

using namespace std;


// 获取当前系统时间
// 头文件 sys/time.h
int64_t getCurrentTime()    
{    
  struct timeval tv;    
  gettimeofday(&tv,NULL);    
  return tv.tv_sec * 1000 + tv.tv_usec / 1000;    
}

void echo_html(int sock, const char* status_code, const char* response)
{
  char erro[MAX/16];
  char path[MAX];

  strcpy(erro+4,".html");
  sprintf(path, "wwwroot%s", erro);

  int fd = open(path, O_RDONLY);

  char header_msg[MAX];
  strcpy(header_msg,response);

  if(send(sock,header_msg, strlen(header_msg), 0) < 0){
    perror("send");
  }
  struct stat st;
  if(stat(path, &st)< 0){
    perror("stat");
  }
  // 构建响应行
  int Content_length = st.st_size;
  char len[MAX];
  sprintf(len, "CONTENT-LENGTH:%d", Content_length);

  //发送响应报头
  if(send(sock,len, strlen(len), 0) < 0){
    perror("send");
  }

  // 发送空行
  const char* msg ="\r\n";
  if(send(sock,msg, strlen(msg), 0)< 0){
    perror("send");
  }

  // 发送响应正文
  if(sendfile(sock,fd, NULL, st.st_size) < 0){
    perror("sendfile");
  }
  close(fd);
}

// 出错处理
void echo_erro(int sock, int status_code)
{
  switch(status_code){
    case 400:
      {
        const char* erro = "400";
        const char* head = "HTTP/1.0 400 Bad Request!";
        echo_html(sock, erro, head);
      }
      break;
    case 403:
      {
        const char* erro = "403";
        const char* head = "HTTP/1.0 403 Forbidden!";
        echo_html(sock, erro, head);
      }
      break;
    case 404:
      {
        const char* erro = "404";
        const char* head = "HTTP/1.0 404 Not Found!";
        echo_html(sock, erro, head);
      }
      break;
    case 500:
      {
        const char* erro = "500";
        const char* head = "HTTP/1.0 400 Internal Server Error!";
        echo_html(sock, erro, head);
      }
      break;
    case 503:
      {
        const char* erro = "503";
        const char* head = "HTTP/1.0 503 Server Unavailable!";
        echo_html(sock, erro, head);
      }
      break;
    default:
      break;
  }
}

// socket三连连
int Start()
{
  int sock = socket(AF_INET, SOCK_STREAM, 0);
  if(sock < 0){
    perror("socket");
    exit(1);
  }
  int opt = 1;
  // 此处设置端口复用
  setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
  struct sockaddr_in local;
  local.sin_family = AF_INET;
  local.sin_addr.s_addr = inet_addr(IP);
  local.sin_port = htons(PORT);

  int bin = bind(sock, (struct sockaddr*)&local, sizeof(local));
  if(bin < 0){
    perror("bind");
    exit(2);
  }

  if(listen(sock, 5) < 0){
    perror("listen");
    exit(3);
  }

  return sock;
}

// 分析HTTP请求的(按行请求)
int GetLine(int sock, char line[], int size)
{
  int i = 0;
  char c = '\0';
  while(c != '\n' && i < size)
  {
    ssize_t s = recv(sock, &c, 1, 0);
    if(s < 0){
      perror("recv");
      return 400;
    }
    else if(s>0){
      if(c == '\r'){
        //使用窥探判断下一个字符是否是'\n'
        recv(sock, &c, 1, MSG_PEEK);
        if(c == '\n'){
          //读到换行'\r\n'
          //直接获取'\n'
          recv(sock,&c,1,0);
        }
        else{
          //不是'\r\n'，将'\r'直接替换成'\n'
          c = '\n';
        }
      }
      line[i] = c;
      ++i;
    }
    else {
      perror("client closed...!");
      return -1;
    }
  }
  line[i] = '\0';
  return i; 

}

// 清理剩余报文
void clean_header(int sock)
{
  char buf[MAX];
  do{
    GetLine(sock,buf,MAX);
  }while(strcmp(buf,"\n") != 0);
}

int find_point(char* str)
{
  char* cur=str;
  while(*str!='.')
  {
    ++str;
  }
  return str-cur;
}

string get_time()//获取当前日期
{
  time_t timep;
  time (&timep);
  char tmp[64];
  strftime(tmp, sizeof(tmp), "%Y-%m-%d",localtime(&timep) );
  return tmp;
}

//读取文件字到string
string read_file_to_string(const char* filename)
{
  ifstream ifile(filename);
  //将文件读入到ostringstream对象buf中
  ostringstream buf;
  char ch;
  while(buf&&ifile.get(ch))
    buf.put(ch); 
  //返回与流对象buf关联的字符串
  return buf.str();
}
//将string写成文件
int write_string_to_file(const char* file, const string& str )
{
  ofstream OsWrite(file);
  OsWrite<<str;
  OsWrite.close();
  return 0;
}

int string_replace(string& str,const char* path)
{
  string mode="img src=\"xxxxx\" alt=\"false\"";
  string sub="img src=\"xxxxx\" alt=\"true\"";
  size_t pos=-1;
  if((pos=str.find(mode))!=string::npos){
    size_t sub_pos=sub.find("xxxxx");
    sub.replace(sub_pos,5,path);
    str.replace(pos, mode.size(), sub);    //用s1替换s中从pos开始（包括0）的n个字符的子串
    return 0;
  }
  return -1;
}

int change_html_to_send(string& str,string& path)
{
  struct dirent *direntp;
  DIR *dirp = opendir(path.c_str());

  struct stat s_buf;
  string image_path="img/fun_images/";
  if (dirp != NULL) {
    while ((direntp = readdir(dirp)) != NULL){
      stat(direntp->d_name,&s_buf);
      if((strcmp(direntp->d_name,"..")!=0) && (strcmp(direntp->d_name,".")!=0)){
        if(string_replace(str,(image_path+direntp->d_name).c_str())!=0)
          break;
      }
    } // end while
  }
  closedir(dirp);
  return 200;
}

int send_html(int sock,char* path)
{
  //下载的图片所在的路径
  string image_path = "source/img/fun_images/";
  Redis *r = new Redis();  
  if(!r->connect("127.0.0.1", 6179)){  
    printf("connect error!\n");  
    return 404;  
  } 
  string today=get_time();
  today+=".html";
  string html_data=r->get(today);
  string road = "/www/wwwroot/ftpuser/http/source/";
  road += today;
  if(html_data.empty()){//redis没有缓存
    html_data = read_file_to_string(path);
    change_html_to_send(html_data,image_path);
    write_string_to_file((road).c_str(),html_data);//将string包含的字节流写成文件
    r->set(today,html_data);
  }
  int fd = open((road).c_str(),O_RDONLY);
  struct stat file;
  stat((road).c_str(),&file);

  if(sendfile(sock,fd,NULL,file.st_size) < 0){
    perror("send...!");
  }
  delete r;
  return 200;
}

static int echo_www(int sock,char* path, int size)
{
  printf("path:%s\n",path);
  int fd = open(path,O_RDONLY);
  char header_msg[MAX];//响应行
  const char* response = "HTTP/1.0 200 OK!\r\n";
  strcpy(header_msg,response);

  if(send(sock,header_msg, strlen(header_msg)+1, 0) < 0){
    perror("send");
  }

  int Content_length = size;//响应报头
  char len[MAX];
  char tail[5] = {0};
  char type[MAX] = {0};
  int index = find_point(path);
  strcpy(tail,path+index+1);
  if(strcmp(tail,"jpg") == 0){
    char str[30]={0};
    strcpy(str,"image/jpeg\r\n");
    sprintf(type, "Content-Type:%s",str);
    printf("type:%s\n",type);
    if(send(sock,type, strlen(type)+1, 0) < 0){//发送响应报头
      perror("send");
    }
  }else if(strcmp(tail,"css") == 0){
    char str[30]={0};
    strcpy(str,"text/css\r\n");
    sprintf(type, "CONTENT-TYPE:%s",str);
    if(send(sock,type, strlen(type)+1, 0) < 0){//发送响应报头
      perror("send");
    }

  }
  else if(strcmp(tail,"js") == 0){
    char str[30]={0};
    strcpy(str,"application/x-javascript\r\n");
    sprintf(type, "CONTENT-TYPE:%s",str);
    if(send(sock,type, strlen(type)+1, 0) < 0){//发送响应报头
      perror("send");
    }

  }else if(strcmp(tail,"gif") == 0){
    char str[30]={0};
    strcpy(str,"image/gif\r\n");
    sprintf(type, "CONTENT-TYPE:%s",str);
    if(send(sock,type, strlen(type)+1, 0) < 0){//发送响应报头
      perror("send");
    }

  }else if(strcmp(tail,"png") == 0){
    char str[30]={0};
    strcpy(str,"image/png\r\n");
    sprintf(type, "CONTENT-TYPE:%s",str);
    if(send(sock,type, strlen(type)+1, 0) < 0){//发送响应报头
      perror("send");
    }

  }else if(strcmp(tail,"html") == 0){
    char str[30]={0};
    strcpy(str,"text/html\r\n");
    sprintf(type, "CONTENT-TYPE:%s",str);
    if(send(sock,type, strlen(type)+1, 0) < 0){//发送响应报头
      perror("send");
    }

  }else if(strcasecmp(tail,"mp4") == 0){
    char str[30]={0};
    strcpy(str,"video/mpeg4\r\n");
    sprintf(type, "CONTENT-TYPE:%s",str);
    if(send(sock,type, strlen(type), 0) < 0){//发送响应报头
      perror("send");
    }

  }
  else if(strcmp(tail,"ico") == 0){
    char str[30]={0};
    strcpy(str,"image/x-icon\r\n");
    sprintf(type, "CONTENT-TYPE:%s",str);
    printf("type:%s\n",type);
    if(send(sock,type, strlen(type), 0) < 0){//发送响应报头
      perror("send");
    }

  }
  printf("length:%d\n",Content_length);
  sprintf(len, "CONTENT-LENGTH:%d\r\n",Content_length);
  
  printf("contnet-len:%s\n",len);
  if(send(sock,len, strlen(len), 0) < 0){//发送响应报头
    perror("send");
  }


  const char* msg ="\r\n";
  if(send(sock,msg, strlen(msg), 0)< 0){//空行
    perror("send");
  }
  
  if(strcmp(path,"source/home.html")==0)
  {
    return send_html(sock,path);//发送正文
  }
  return sendfile(sock,fd,NULL,size);
}


int echo_cgi(int sock,char method[],char path[],char* query_string)
{
  char line[MAX];
  int  Content_length = -1;
  char cont_len_env[MAX];
  char method_env[MAX/16];
  char query_string_env[MAX];

  if(strcasecmp(method, "GET") == 0){//是GET方法,去掉头部
    clean_header(sock);
  }
  else{//POST方法,丢弃头部，提取Content-length

    do{
      GetLine(sock,line, MAX);
      if(strncasecmp(line,"CONTENT-LENGTH",16) == 0){
        Content_length = atoi(line+16);
      }
    }while(strcmp(line,"\n")); 
    if(Content_length == -1)
    {
      //出错 
      return 404; 
    }
  }

  const char* msg = "HTTP/1.0 200 OK";
  if(send(sock, msg, strlen(msg), 0) < 0){
    perror("send");
  }
  const char* Content = "Content-Type:test/html";
  if(send(sock,Content,strlen(Content), 0) < 0){
    perror("send");
  }
  const char* empty = "\r\n";
  if(send(sock,empty,strlen(empty),0) < 0){
    perror("send");
  }
  //1.开始发送正文
  //2.POST方法，父进程将正文部分数据给子进程
  //3.子进程进行进程替换,将执行结果放回管道,给父进程
  int input[2];//子进程
  int output[2];

  pipe(input);
  pipe(output);

  pid_t id = fork();
  if(id < 0){
    perror("fork");
    return 500;
  }
  else if(id == 0){//子进程
    //1.判断是GET方法还是POST方法
    close(input[1]);//关闭子进程写
    close(output[0]);//关闭父进程度
    //进行进程替换,但是将方法和参数都传给子进程,需要将其导入环境变量
    //又环境变量不会被进程替换,因此需要将其导出

    sprintf(method_env,"METHOD=%s",method);
    putenv(method_env);
    printf("method:%s\n",method); 
    if(strcasecmp(method,"GET") == 0){
      sprintf(query_string_env,"QUERY_STRING=%s",query_string);
      printf("%s\n",query_string_env);
      if(putenv(query_string_env)){
        perror("putenv");
      }
    }
    else{
      sprintf(cont_len_env,"CONTENT_LENGTH=%d",Content_length);
      putenv(cont_len_env);
      printf("%s\n",cont_len_env);
    }
    dup2(input[0],0);//从标准输入里读,父进程读到数据写入了子进程的管道,此时子进程从标准输入里读取数据
    dup2(output[1],1);//往父进程管道里写,父进程写到标准输出
    execl(path,path,NULL);//进程替换,执行可执行程序
    perror("execl");
    exit(1);
  }
  else{//父进程
    close(input[0]);//关闭子进程的读
    close(output[1]);//关闭父进程的读

    signal(SIGPIPE,SIG_IGN);
    char c = 'a';
    if(strcasecmp(method, "POST") == 0){
      int i = 0;
      for(; i < Content_length; ++i){//将读取到的数据写道子进程的管道
        recv(sock, &c, 1, 0);
        write(input[1],&c,1);
      }
    }

    while(read(output[0], &c, 1) > 0){//从父进程的管道当中读取数据写会浏览器
      send(sock, &c, 1, 0);
    }
    close(input[1]);
    close(output[0]);
    waitpid(id, NULL, 0);
  }
  return 200;
}

// 更新商品价格
void update_price_html(const char* file)
{
    string str = read_file_to_string(file); 
    Redis *r = new Redis();  
    if(!r->connect("127.0.0.1", 6179)){  
      printf("connect error!\n");  
    }
    string time = get_time();
    string price = r->get(time);
    if(price=="finish"){
      return;
    }
    r->set(time, "finish");
    string mode="future: true }]";
    string sub="{ label: \'";
    sub+=time;
    sub+="\', value: ";
    sub+=price;
    sub+=", future: true }]";
    size_t pos=-1;
    if((pos=str.find(mode)) != string::npos){
      str.replace(pos+mode.size()-1,1, ",");    //用s1替换s中从pos开始（包括0）的n个字符的子串
      str.replace(pos+mode.size(),0, sub);    //用s1替换s中从pos开始（包括0）的n个字符的子串
    }
    write_string_to_file(file, str);
    delete r;
}

void hander(int sock, int epoll_fd)
{
  char line[MAX];     // 按行读取 HTTP 报文
  char method[MAX/16];// HTTP 请求的方法
  char url[MAX];      //url资源
  char path[MAX];     //路径
  char* query_string; //参数
  int  status_code = 200;//状态码
  int  cgi = 0;

  // 1. 读取 HTTP 报文信息
  int ret = GetLine(sock, line, sizeof(line));
  if(ret  <= 0){
    status_code = 400;
    if(status_code != 200){
      echo_erro(sock,status_code);
    }
    // 1.1 读取报文失败, 从时间表中删除此事件, 并关闭文件描述符
    epoll_ctl(epoll_fd, EPOLL_CTL_DEL, sock, NULL);
    close(sock);
    return;
  }

  // 2. 读取成功， 开始分析数据
  size_t  i = 0;
  size_t  j = 0;

  // 2.1 获得该请求的方法(GET or POST)
  while((i < sizeof(line)-1) && j < sizeof(method)-1 && !isspace(line[j])){//以空格为分割,读取方法
    method[j] = line[i];
    j++,i++;
  }
  method[j] = '\0';

  j = 0;
  i+=1; 
  // 2.2 获取 url
  while((i < sizeof(url)-1) && !isspace(line[i])){
    url[j] = line[i];
    ++j,++i;
  }
  url[j] = '\0';

  // 3. 处理获得的数据
  // 3.1 GET 方法
  if(strcasecmp(method, "GET") == 0){
    //此时是GET方法，判断有没有参数，如果有参数，是cgi模式
    //url里面有？表示有参数，提取参数和路径
    query_string = url;//获取参数
    while(*query_string != '\0'){

      if(*query_string == '?'){//表示有参数
        *query_string = '\0';
        cgi = 1;
        ++query_string;
        break;
      }
      ++query_string;
    }
  } // end if GET

  // 3.2 POST 方法
  else if(strcasecmp(method, "POST") == 0){
    cgi = 1; // cgi处理
  }
  // 3.3 出错处理
  else{
    status_code = 400;
    clean_header(sock);
    epoll_ctl(epoll_fd, EPOLL_CTL_DEL, sock, NULL);
    close(sock);
    return;
  }

  sprintf(path,"source%s",url);

  // 访问根目录
  if(path[strlen(path)-1] == '/'){
    // 连接 redis 数据库
    Redis *r = new Redis();  
    if(!r->connect("127.0.0.1", 6179)){  
      printf("connect redis error!\n");  
    }
    // 获取当前时间
    string today=get_time();
    string html_data=r->get(today);
    if(html_data.empty()){//redis没有缓存
      strcat(path,"home.html");
    }
    else {
      strcat(path,(today+".html").c_str());
    }
    delete r;
  }
  printf("path:%s \n",path);
  struct stat buf;
  // 访问的资源不存在
  if(stat(path, &buf) < 0){
    status_code = 404;
    clean_header(sock);
    epoll_ctl(epoll_fd, EPOLL_CTL_DEL, sock, NULL);
    close(sock);
    return;
  }
  else{
    if(strcmp(url,"/price.html")==0){
      update_price_html(path);
    }
    if(S_ISDIR(buf.st_mode)){
      strcat(path,"home.html");
    }
    else if(S_ISREG(buf.st_mode)){
      if((buf.st_mode & S_IXGRP) || (buf.st_mode & S_IXUSR) || (buf.st_mode & S_IXOTH)){
        cgi = 1;
      }
    }
    else{
    }
  }

  printf("cgi:%d\n",cgi);
  if(cgi == 0){//开始响应,此时表示是GET方法,且目录绝对存在
    clean_header(sock);
    status_code = echo_www(sock,path,buf.st_size);
  }
  else{//cgi处理
    status_code = echo_cgi(sock,method,path,query_string);     
  }

end:
  if(status_code != 200){
    echo_erro(sock,status_code);
  }
  epoll_ctl(epoll_fd, EPOLL_CTL_DEL, sock, NULL);
  close(sock);
}

void setNonBlock(int fd)
{
  int fl = fcntl(fd, F_GETFL);
  fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}

// 处理读事件
void handlerReadyEvents(int epoll_fd, struct epoll_event revents[], int num, int listen_sock)
{
  int i = 0;
  //struct epoll_event ev;
  for(; i < num; ++i){
    int fd = revents[i].data.fd;
    uint32_t events = revents[i].events;
    // 1. 来了新的连接请求
    if(listen_sock == fd && (events & EPOLLIN)){
      struct epoll_event ev;
      int sock = accept(fd,NULL,NULL);
      if(sock < 0){
        perror("accept");
      }
      printf("get new client..., connectfd : %d\n", sock);
      ev.data.fd = sock;
      ev.events = EPOLLIN;
      if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sock, &ev)==-1)
      {
        perror("epoll_ctl add connect");
        return;
      }
    }
    // 2. 有数据可读了
    else if(events & EPOLLIN){
      hander(fd, epoll_fd); 
    }
  }
}

int main()
{
  // 屏蔽 SIGPIPE 信号
  signal(SIGPIPE, SIG_IGN);
  int listen_fd = Start();

  int epoll_fd = epoll_create(65535);
  if(epoll_fd < 0){
    perror("epoll_create");
    return 1;
  }

  struct epoll_event events;
  events.data.fd = listen_fd;
  events.events = EPOLLIN;

  int ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &events);
  if(ret < 0){
    perror("epoll_ctl");
    return 2;
  }

  struct epoll_event revents[2048];
  for(; ; ){
    int timeout = -1;
    int num = epoll_wait(epoll_fd,revents,sizeof(revents)/sizeof(revents[0]),timeout);
    switch(num){
      case -1:
        perror("epoll_wait");
        break;
      case 0:
        printf("timeout.......\n");
        break;
      default:
          handlerReadyEvents(epoll_fd, revents,num,listen_fd);
          break;
    }
  }
  close(listen_fd);
  close(epoll_fd);
} 
